/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ec.cd.server;

import ec.cd.common.Networking;
import ec.cd.common.Sensors;
import ec.cd.server.gui.GraphFrame;
import ec.cd.server.gui.GraphThread;

import java.io.*;
import java.net.Socket;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author endri
 */
public class ClientHandleThread extends Thread {

    private Socket clientSocket;
    private int runningMode;
    private ServerBlocking.ConfigInfo configInfo;
    private ContextDetection contextDetection;
    private long refTime;
    private ArrayList<Sensors.SensorData> accData, gyrData;
    private Networking.BodyLocation bodyLocation;
    //private GraphFrame graphFrame;
    private GraphThread graphThread;

    /**
     *
     * @param clientSocket
     * @param runningMode
     * @param configInfo
     */
    public ClientHandleThread(Socket clientSocket, int runningMode, ServerBlocking.ConfigInfo configInfo) {
        this.clientSocket = clientSocket;
        this.runningMode = runningMode;
        this.configInfo = configInfo;
        if(configInfo == null)
            configInfo = new ServerBlocking.ConfigInfo();
        contextDetection = new ContextDetection(configInfo.accRangesTorso,
                configInfo.gyrRangesTorso, configInfo.accRangesArm,
                configInfo.gyrRangesArm, configInfo.accRangesLeg,
                configInfo.gyrRangesLeg); //careful for nulls, when we don't have ranges yet
        refTime = System.currentTimeMillis();
        graphThread = new GraphThread();
        
    }

    @Override
    public void run() {
        ObjectInputStream objectInputStream = null;
        ObjectOutputStream objectOutputStream = null;
        try {
            objectInputStream = new ObjectInputStream(clientSocket.getInputStream());
            objectOutputStream = new ObjectOutputStream(clientSocket.getOutputStream());
            try {
                Networking.Message helloMessage = (Networking.Message) objectInputStream.readObject();
                
                Networking.ClientHello clientHello = (Networking.ClientHello) helloMessage.payload;
                System.out.println("Received hello message from:" + clientHello.bl);
                bodyLocation = clientHello.bl;
                graphThread.initialize(bodyLocation);
                graphThread.start();
            } catch (Exception ex) {
                ex.printStackTrace();
                System.exit(1);
            }
            //now we start the communication protocol
            //if the mode is calibration, then the sensors will send everything and in the fastest mode
            //because we need to have all the data.
            //if it is detection mode, the range will be included, and of course some other rate will be selected.
            //first the server will send the config to the client
            //the config should be a set of 
            if (runningMode == ServerBlocking.CALIBRATION_RUN) {
                accData = new ArrayList<Sensors.SensorData>();
                gyrData = new ArrayList<Sensors.SensorData>();
                System.out.println("Sending calibration message...");
                sendCalibrationMessage(objectOutputStream);
                for (int stateCalibrating = 1; stateCalibrating <= 3; stateCalibrating++) {
                	System.out.println("Waiting for data from: " + bodyLocation);
                    receiveSaveTraceCalibration(objectInputStream, "accTrace" + stateCalibrating + ".txt", "gyrTrace" + stateCalibrating + ".txt");
                    //handing control to calibration
                    contextDetection.calibrate(accData, gyrData, stateCalibrating, bodyLocation);
                }
            } else if (runningMode == ServerBlocking.DETECTION_RUN) {
                sendConfigMessage(objectOutputStream, configInfo.lengthOfTest);
                try {
                    for (; receiveMessage(objectInputStream, null, null););
                } catch (ClassNotFoundException ex) {
                    Logger.getLogger(ClientHandleThread.class.getName()).log(Level.SEVERE, null, ex);
                    ex.printStackTrace(); //for debugging
                }
            }
        } catch (IOException ex) {
            Logger.getLogger(ServerBlocking.class.getName()).log(Level.SEVERE, null, ex);
            ex.printStackTrace();
            System.exit(1);
        } 
    }

    private void receiveSaveTraceCalibration(ObjectInputStream objectInputStream, String nameFileAcc, String nameFileGyr) throws IOException {
        File fileAcc = new File(nameFileAcc);
        File fileGyr = new File(nameFileGyr);
        if (!fileAcc.exists()) {
            fileAcc.createNewFile();
        }
        if (!fileGyr.exists()) {
            fileGyr.createNewFile();
        }
        FileWriter fileWriterAcc = new FileWriter(fileAcc);
        BufferedWriter writerAcc = new BufferedWriter(fileWriterAcc);
        FileWriter fileWriterGyr = new FileWriter(fileGyr);
        BufferedWriter writerGyr = new BufferedWriter(fileWriterGyr);
        try {
            for (; receiveMessage(objectInputStream, writerAcc, writerGyr););
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(ClientHandleThread.class.getName()).log(Level.SEVERE, null, ex);
            ex.printStackTrace();
        }
        writerAcc.close();
        writerGyr.close();
        fileWriterAcc.close();
        fileWriterGyr.close();
    }

    private void sendConfigMessage(ObjectOutputStream objectOutputStream, int lengthOfEachState) throws IOException {
        Networking.Message message = new Networking.Message();
        message.mt = Networking.MessageType.ServerNormalRun;
        Sensors.SensorConfig sensorConfigs[] = new Sensors.SensorConfig[2];
        sensorConfigs[0] = new Sensors.SensorConfig();
        sensorConfigs[0].sensorType = Sensors.ST_ACCELEROMETER;
        sensorConfigs[0].sensorSamplingRate = configInfo.samplingRate;


        sensorConfigs[1] = new Sensors.SensorConfig();
        sensorConfigs[1].sensorType = Sensors.ST_GYROSCOPE;
        sensorConfigs[1].sensorSamplingRate = configInfo.samplingRate;
        switch (bodyLocation) {
            case Torso:
                sensorConfigs[0].ranges = configInfo.accRangesTorso;
                sensorConfigs[1].ranges = configInfo.gyrRangesTorso;
                break;
            case Arm:
                sensorConfigs[0].ranges = configInfo.accRangesArm;
                sensorConfigs[1].ranges = configInfo.gyrRangesArm;
                break;
            case Leg:
                sensorConfigs[0].ranges = configInfo.accRangesLeg;
                sensorConfigs[1].ranges = configInfo.gyrRangesLeg;
                break;
        }


        message.payload = new Networking.ServerNormalRun();
        ((Networking.ServerNormalRun) message.payload).scs = sensorConfigs;
        if (runningMode == ServerBlocking.POWERSAVE_RUN) {
            ((Networking.ServerNormalRun) message.payload).sending_rate = 0;
        } else {
            ((Networking.ServerNormalRun) message.payload).sending_rate = configInfo.sendingRate;
        }
        objectOutputStream.writeObject(message);
        objectOutputStream.flush();
    }

    private void sendCalibrationMessage(ObjectOutputStream objectOutputStream) throws IOException {
        Networking.Message message = new Networking.Message();
        message.mt = Networking.MessageType.ServerCalibrationRun;
        Networking.ServerCalibrationRun scr = new Networking.ServerCalibrationRun();
        scr.testLength = configInfo.lengthOfTest;
        scr.phaseNames = new String[]{"Sitting", "Walking", "Running"};
        message.payload = scr;
        objectOutputStream.writeObject(message);
        objectOutputStream.flush();
    }

    private void handleCalibrationData(Networking.Message message,
            BufferedWriter writerAcc,
            BufferedWriter writerGyr) throws IOException {
    	Networking.ClientSendData clientSendData = (Networking.ClientSendData) message.payload;
        for (Sensors.SensorData sensorData : clientSendData.sensors_data ) {
            switch (sensorData.sensorType) {
                case Sensors.ST_ACCELEROMETER:
                    accData.add(sensorData);
                    writerAcc.append(sensorData.toString());
                    break;
                case Sensors.ST_GYROSCOPE:
                    gyrData.add(sensorData);
                    writerGyr.append(sensorData.toString());
                    break;
            }
        }
        //outputStreamAcc.flush();
        //outputStreamGyr.flush();
    }

    /*
     * This method should return false only if an ClientBye message is received.
     */
    private boolean receiveMessage(ObjectInputStream objectInputStream, BufferedWriter outputStreamAcc,
            BufferedWriter outputStreamGyr) throws IOException, ClassNotFoundException {
    	
        Networking.Message message = (Networking.Message) objectInputStream.readObject();

        switch (message.mt) {
            case ClientSendData:
            	System.out.println("Received data from: " + bodyLocation);
                Networking.ClientSendData clientSendData = (Networking.ClientSendData) message.payload;
                giveDataToGraphs(message);
                
                //most prob this data will need to be published to the gui for the graphs
                if (runningMode == ServerBlocking.CALIBRATION_RUN) {
                    //ToDo: Save the data in two sepparate files, one for acc and one for gyro, in append mode
                    handleCalibrationData(message, outputStreamAcc, outputStreamGyr);
                    //the message should also pass to the GUI so that the graph can function
                } else {
                    //Here the data will be handed to the context detection


                    int context = contextDetection.checkContext(clientSendData.sensors_data, bodyLocation);
                    //this context has to be reported to GUI
                    graphThread.addContext(context);
                }
                break;
            case ClientBye:
            	System.out.println("Received Bye from: " + bodyLocation);
                return false;
        }
        return true;
    }

    private void giveDataToGraphs(Networking.Message message) {
        Networking.ClientSendData clientSendData = (Networking.ClientSendData) message.payload;
        for (Sensors.SensorData sensorData : clientSendData.sensors_data) {
            graphThread.addSensorData(sensorData);
            System.out.println("Received type: " + sensorData.sensorType);
        }
    }
}
